/**
 * 
 */
package v2;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.ArrayList;
import java.util.Random;
import java.util.Scanner;

/**
 * @author nico
 *
 */
public class Terrain {
	
	///////////////
	// Attributs //
	///////////////
	
	/**
	 * la matrice de parcelle representant mon monde
	 */
	Parcelle[][] terrain;

	/**
	 * l'arraylist de depressions que l'on va faire bouger alétoirement chaque tour
	 */
	ArrayList<Depression> mesPrecipitations;
	
	/**
	 * la probabilité de faire agir une depression (dépend de la taille du terrain)
	 */
	double probaDepr;
	
	///////////////////
	// COnstructeurs //
	///////////////////
	
	/**
	 *  Méthode permettant de construire un terrain avec des depressions, parcelles, animaux et végétaux placés aléatoirements
	 *  
	 *  @param nbLignes le nombre de lignes de ma matrice
	 *  @param nbColonnes le nombre de colonnes de ma matrice
	 *  @param monType1 le premier de mes type de parcelle
	 *  @param proportionType1 la proportion de ce type de parcelle présent
	 *  @param monType2 le second type de parcelle
	 *  @param proportionType2 la proportion de ce type de parcelle présent
	 *  @param monType3 le troisieme type de parcelle
	 *  @param proportionType3 la proportion de ce type de parcelle présent
	 *  @param monVeg1 le premier type de végétal présent
	 *  @param proportionVeg1 la proportion de ce type de vegetal présent
	 *  @param Veg2 le second type de végétal présent
	 *  @param proportionVeg2 la proportion de ce type de vegetal présent
	 *  @param Veg3 le troisieme type de végétal présent
	 *  @param proportionVeg3 la proportion de ce type de vegetal présent
	 *  @param monAni1 le premier type d'animal présent 
	 *  @param proportionAni1 la proportion de ce type d'animal présent
	 *  @param monAni2 le second type d'animal présent
	 *  @param proportionAni2 la proportion de ce type d'animal présent
	 *  @param monAni3 le troisieme type d'animal présent
	 *  @param proportionAni3 la proportion de ce type d'animal présent
	 *  @param nbPrecip le nombre de précipitations dans le monde
	 */
	// proportionType1+proportionType2+proportionType3 doit etre egal a 1...
	public Terrain(int nbLignes, int nbColonnes, TypeParcelle monType1, double proportionType1, TypeParcelle monType2, double proportionType2, TypeParcelle monType3, double proportionType3, TypeVegetal monVeg1, double proportionVeg1, TypeVegetal monVeg2, double proportionVeg2, TypeVegetal monVeg3, double proportionVeg3, TypeAnimal monAni1, double proportionAni1, TypeAnimal monAni2, double proportionAni2, TypeAnimal monAni3, double proportionAni3, int nbPrecip)
	{
		// la probabilité qu'une depression agisse
		this.probaDepr=1/(nbLignes + nbColonnes);
		
		terrain = new Parcelle[nbLignes][nbColonnes]; 
		for (int i = 0;i<nbLignes;i++) // ligne de parcelles
		{
			for (int j = 0;j<nbLignes;j++) // colonne de parcelles
			{
				// mes types
				TypeParcelle monType;
				TypeVegetal monTypeVeg;
				TypeAnimal monTypeAni;
				
				// mes objets
				Vegetaux monVeg;
				Animaux monAni;
				int monHygro;
				Position maPosition = new Position(i,j);
				
				// objet temporaire
				Random monRand = new Random();
				
				// determination du type de parcelle
				double monTirage = Math.random();
				if (monTirage<proportionType1)
				{
					monType = monType1;
				}
				else
				{
					if ((proportionType1 <= monTirage) && (monTirage<proportionType1+proportionType2))
					{
						monType = monType2;
					}
					else
					{
						monType = monType3;
					}
				}
				
				// determination dd l'Hygro
				int min  = monType.minHygro();
				int max = monType.maxHygro();
				double standardDev = (max-min)/2;
				monHygro = (int) ((min + standardDev)+monRand.nextGaussian()*standardDev);
				
				// determination s'il y a un vegetal et quel est son type
				monTirage = Math.random();
				if (monTirage<proportionVeg1)
				{
					monVeg = new Vegetaux((int)(monVeg1.maxSante()/2 + monRand.nextGaussian()*(monVeg1.maxSante()/2)), (int) (monVeg1.minEau()+(monVeg1.maxEau()-monVeg1.minEau())/2 + monRand.nextGaussian()*(monVeg1.maxEau()-monVeg1.minEau())/2), maPosition, monVeg1);
				}
				else
				{
					if ((proportionVeg1<= monTirage) && (monTirage<proportionVeg1+proportionVeg2))
					{
						monVeg = new Vegetaux((int)(monVeg2.maxSante()/2 + monRand.nextGaussian()*(monVeg2.maxSante()/2)), (int) (monVeg2.minEau()+(monVeg2.maxEau()-monVeg2.minEau())/2 + monRand.nextGaussian()*(monVeg2.maxEau()-monVeg2.minEau())/2), maPosition, monVeg2);
					}
					else
					{
						if ((proportionVeg1+proportionVeg2<= monTirage) && (monTirage<proportionVeg1+proportionVeg2+proportionVeg3))
						{
							monVeg = new Vegetaux((int)(monVeg3.maxSante()/2 + monRand.nextGaussian()*(monVeg3.maxSante()/2)), (int) (monVeg3.minEau()+(monVeg3.maxEau()-monVeg3.minEau())/2 + monRand.nextGaussian()*(monVeg3.maxEau()-monVeg3.minEau())/2), maPosition, monVeg3);
						}
						else
						{
							monVeg = null;
						}
					}
				}
				
				// determination si il y a un animal et quel est son type
				monTirage = Math.random();
				if (monTirage<proportionAni1)
				{
					
					monAni = new Animaux( (int) (monAni1.maxSante()/2+ monRand.nextGaussian()*monAni1.maxSante()/2), (int) (monAni1.minEau()+(monAni1.maxEau()-monAni1.minEau())/2+monRand.nextGaussian()*(monAni1.maxEau()-monAni1.minEau())/2), maPosition, (int) (monAni1.minNourriture()+(monAni1.maxNourriture()-monAni1.minNourriture())/2+ monRand.nextGaussian()*(monAni1.maxNourriture()-monAni1.minNourriture())/2), monRand.nextBoolean(), monAni1);
				}
				else
				{
					if ((proportionAni1<= monTirage) && (monTirage<proportionAni1+proportionAni2))
					{
						monAni = new Animaux( (int) (monAni2.maxSante()/2+ monRand.nextGaussian()*monAni2.maxSante()/2), (int) (monAni2.minEau()+(monAni2.maxEau()-monAni2.minEau())/2+monRand.nextGaussian()*(monAni2.maxEau()-monAni2.minEau())/2), maPosition, (int) (monAni2.minNourriture()+(monAni2.maxNourriture()-monAni2.minNourriture())/2+ monRand.nextGaussian()*(monAni2.maxNourriture()-monAni2.minNourriture())/2), monRand.nextBoolean(), monAni2);
					}
					else
					{
						if ((proportionAni1+proportionAni2<= monTirage) && (monTirage<proportionAni1+proportionAni2+proportionAni3))
						{
							monAni = new Animaux( (int) (monAni3.maxSante()/2+ monRand.nextGaussian()*monAni3.maxSante()/2), (int) (monAni3.minEau()+(monAni3.maxEau()-monAni3.minEau())/2+monRand.nextGaussian()*(monAni3.maxEau()-monAni3.minEau())/2), maPosition, (int) (monAni3.minNourriture()+(monAni3.maxNourriture()-monAni3.minNourriture())/2+ monRand.nextGaussian()*(monAni3.maxNourriture()-monAni3.minNourriture())/2), monRand.nextBoolean(), monAni3);
						}
						else
						{
							monAni = null;
						}
					}
				}
				
				// Construction de la parcelle
				if (monAni == null) // pas d'animal
				{
					if (monVeg == null) // pas d'animal, pas de végétal
					{
						terrain[i][j]= new Parcelle(monHygro,maPosition,monType);
					}
					else // pas d'animal, vegetal
					{
						terrain[i][j]= new Parcelle(monHygro, maPosition, monType, monVeg); 
						
					}
				}
				else // animal
				{
					if (monVeg == null) // animal, pas de vegetal
					{
						terrain[i][j]= new Parcelle(monHygro, maPosition, monType, monAni);
					}
					else // animal, vegetal
					{
						terrain[i][j]= new Parcelle(monHygro, maPosition, monType, monAni, monVeg);
					}
				}
			}
		}
	}
		
	/////////////
	// Getters //
	/////////////
	
	/**
	 * Permet de retourner la matrice de parcelles du terrain
	 * 
	 * @return the terrain
	 */
	public Parcelle[][] obtientTerrain() {
		return terrain;
	}
	
	/**
	 * Méthode retournant une parcelle à partir de ses coordonnées
	 * 
	 * @param x l'indice de la ligne de la parcelle recherchée
	 * @param y l'indice de la colonne de la parcelle recherchée
	 * @return la parcelle se trouvant aux coordonnées
	 * @throws Exception la parcelle est introuvables (mauvaise coordonnées)
	 */
	public Parcelle obtientParcelle(int x, int y) throws Exception
	{
		try
		{
			return this.terrain[x][y];
		}
		catch (Exception e)
		{ 
			String message;
			if ((x<0) || (x>=terrain.length))
			{
				message = new String("numéro de ligne invalide: "+x+" (nb lignes terrain = "+terrain.length+")");
			}
			else
			{
				if ((y<0) || (y>=terrain[0].length))
				{
					message = new String("numéro de ligne invalide: "+y+" (nb lignes terrain = "+terrain[0].length+")");
				}
				else
				{
					message = new String("Erreur inconnue!");
				}

			}
			throw new Exception("Terrain.obtientParcelle: "+message);
		}
	}
	
	//////////////
	// Méthodes //
	//////////////

	/**
	 * Méthode generant l'arraylist contenant les parcelles à portée
	 * 
	 * @param portee le rayon dans lequel je séléctionne les parcelles
	 * @param monCentre le centre autour duquel je recupere les parcelles à portée
	 * @return L4ensmble des parcelles a portée
	 */
	public ArrayList<Parcelle> obtientParcellesPortee(int portee, Position monCentre)
	{
		ArrayList<Parcelle> mesParcelles = new ArrayList<Parcelle>();
				
		for(int i = monCentre.obtientLigne()-portee; i<=monCentre.obtientLigne()+portee; i++)
		{ 
			for (int j = monCentre.obtientColonne()-portee; j<=monCentre.obtientColonne()+portee; j++)
			{
				if ((i>=0) && (i<this.terrain.length) && (j>=0) && (j<this.terrain[0].length))
				{
					if(Position.calculDistance(terrain[i][j].obtientMaPosition(), monCentre)<=portee)
					{
						mesParcelles.add(terrain[i][j]);
					}
				}
			}
		}
		return mesParcelles;
	}
		
	/**
	 * Méthode permettant de creer un Terrain à partir d'un fichier contenant les données
	 * 
	 * @param emplacementFichier le chemin absolu du fichier que l'on veux charger
	 * @return le Terrain que je viens de créer
	 */
	public static Terrain chargementTerrain(String emplacementFichier)
	{
		// J'ouvre mon fichier
		Scanner scanner;
		try {
			scanner = new Scanner(new File(emplacementFichier));
			
			// je les mes lignes dans un ordre donné
			
			int nbLignes = Integer.valueOf(scanner.nextLine());
			int nbColonnes = Integer.valueOf(scanner.nextLine());
			
			// Parcelles
			TypeParcelle monType1 = TypeParcelle.valueOf(scanner.nextLine());
			double proportionType1 = Double.valueOf(scanner.nextLine());
			TypeParcelle monType2 = TypeParcelle.valueOf(scanner.nextLine());
			double proportionType2 = Double.valueOf(scanner.nextLine());
			TypeParcelle monType3 = TypeParcelle.valueOf(scanner.nextLine());
			double proportionType3 = Double.valueOf(scanner.nextLine());
			
			// Végétaux
			TypeVegetal monVeg1 = TypeVegetal.valueOf(scanner.nextLine());
			double proportionVeg1 = Double.valueOf(scanner.nextLine());
			TypeVegetal monVeg2 = TypeVegetal.valueOf(scanner.nextLine());
			double proportionVeg2 = Double.valueOf(scanner.nextLine());
			TypeVegetal monVeg3 = TypeVegetal.valueOf(scanner.nextLine());
			double proportionVeg3 = Double.valueOf(scanner.nextLine());
			
			// Animaux
			TypeAnimal monAni1 = TypeAnimal.valueOf(scanner.nextLine());
			double proportionAni1 = Double.valueOf(scanner.nextLine());
			TypeAnimal monAni2 = TypeAnimal.valueOf(scanner.nextLine());
			double proportionAni2 = Double.valueOf(scanner.nextLine());
			TypeAnimal monAni3 = TypeAnimal.valueOf(scanner.nextLine());
			double proportionAni3 = Double.valueOf(scanner.nextLine());
			
			// Nombre de depressions
			int nbPrecip = Integer.valueOf(scanner.nextLine());
			
			// fermeture du fichier
			scanner.close();
			
			return new Terrain(nbLignes, nbColonnes, monType1, proportionType1, monType2, proportionType2, monType3, proportionType3, monVeg1, proportionVeg1, monVeg2, proportionVeg2, monVeg3, proportionVeg3, monAni1, proportionAni1, monAni2, proportionAni2, monAni3, proportionAni3, nbPrecip);

		} catch (FileNotFoundException e) {
			e.printStackTrace();
		}
		// NON ATTEIGNABLE
		return null;
	}
	
	// TODO: méthode de sauvegarde de terrain dans un fichier
	// TEST --> TEMPORAIRE, A TESTER
	public void sauvegardeTerrain(String emplacementFichier)
	{
		
        FileOutputStream fos;
		try {
			// création d'un objet FileOutputStream
			fos = new FileOutputStream(emplacementFichier);
			
	        // création d'un objet ObjectOutputStream et utilisation du FileOutputStream comme stream de sortie
	        ObjectOutputStream oos = new ObjectOutputStream(fos);

	        // sauvegarde du terrain
	        oos.writeObject(this);

	        //fermeture de l'outputStream
	        oos.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Méthode permettant de faire un "tour" du terrain
	 */
	public void genererUnTour()
	{
		// un objet Random que j'utilise par la suite
		Random monRand = new Random();
		
		////////////////
		// DEPRESSION //
		////////////////
		
		// je commence par gerer les dépressions
		// --> probabilité d'action de ma depressions depant de la taille du terrain
		if (this.probaDepr <= monRand.nextDouble())
		{
			// le depression que je vais faire agir
			int numPrecip = monRand.nextInt(mesPrecipitations.size());
			Depression maDepr = mesPrecipitations.get(numPrecip);

			// je commence à faire pleuvoir
			maDepr.augmenterHygro(this.obtientParcellesPortee(maDepr.obtientRayon(), maDepr.obtientPosition()));
			
			// je calcul le deplacement de ma precipitation et je la fais se deplacer 
			if ((maDepr.obtientPosition().obtientLigne()+1 >= terrain.length) || (maDepr.obtientPosition().obtientColonne()+1 >= terrain[0].length))
			{
				// si une depression disparait, j'en rajoute une sur un des coté de la carte!
				int monRay = 0, monInt = 0, typDim = 0;

				// je calcul le rayon moyen, l'intensité moyenne, et le typDim moyen(???)
				for (int z =0;z < mesPrecipitations.size();z++)
				{
					monRay+=mesPrecipitations.get(z).obtientRayon();
					monInt+=mesPrecipitations.get(z).obtientIntensite();
					typDim+=mesPrecipitations.get(z).obtientTypDim();
				}
				
				// je fais la moyenne, je la multiplie par 2, je tire un nombre aléatoire gaussien et je l'assigne à mes valeurs...
				monRay= (int) (monRay/mesPrecipitations.size() + monRand.nextGaussian()*monRay/mesPrecipitations.size());
				monInt= (int) (monInt/mesPrecipitations.size() + monRand.nextGaussian()*monInt/mesPrecipitations.size());
				typDim= (int) (typDim/mesPrecipitations.size() + monRand.nextGaussian()*typDim/mesPrecipitations.size());

				// je supprime la depression...
				terrain[maDepr.obtientPosition().obtientLigne()][maDepr.obtientPosition().obtientColonne()].assigneMaDepression(null);
				mesPrecipitations.remove(numPrecip);
				
				// je creé la nouvelle depression
				if (monRand.nextBoolean())
				{
					// je fais rentrer la precipitation sur la ligne 0 et la colonne X
					mesPrecipitations.add(new Depression(new Position(0, monRand.nextInt(terrain[0].length)), monRay, monInt, typDim));
				}
				else
				{
					// je fais rentrer la precipitation sur la ligne X et la colonnes 0
					mesPrecipitations.add(new Depression(new Position(monRand.nextInt(terrain[0].length), 0), monRay, monInt, typDim));
				}
				
				// je met ma nouvelle depression sur la bonne parcelle...
				terrain[mesPrecipitations.get(mesPrecipitations.size()-1).obtientPosition().obtientLigne()][mesPrecipitations.get(mesPrecipitations.size()-1).obtientPosition().obtientColonne()].assigneMaDepression(mesPrecipitations.get(mesPrecipitations.size()-1));
			}
			else
				// MA DEPRESSION NE SORT PAS DU TERRAIN
			{
				// je fais bouger ma depression
				// changement de la position de ma depression
				maDepr.assignePosition(terrain[maDepr.obtientPosition().obtientLigne()+1][maDepr.obtientPosition().obtientColonne()+1].obtientMaPosition());
				
				// changement de la depression sur le tableau
				terrain[maDepr.obtientPosition().obtientLigne()-1][maDepr.obtientPosition().obtientColonne()-1].assigneMaDepression(null);
				terrain[maDepr.obtientPosition().obtientLigne()][maDepr.obtientPosition().obtientColonne()].assigneMaDepression(mesPrecipitations.get(numPrecip));
			}
		}
		
		///////////////
		// PARCELLES //
		///////////////
		// je gère maintenant une parcelle pour les animaux, une pour les végétaux...
		
		

		/////////////
		// ANIMAUX //
		/////////////

		boolean aAgi = false;
		int ligne;
		int colonne;
		
		while(!aAgi)
		{
		
			ligne = monRand.nextInt(this.terrain.length);
			colonne = monRand.nextInt(this.terrain[0].length);
	
			if (this.terrain[ligne][colonne].estAnimal())
			{
				Animaux monAni = this.terrain[ligne][colonne].obtientMonAnimal();
				
				//PAS FORCEMENT UTILE!!!
				// je commence par vérifier si mon animal est encore en vie --> PAS FORCEMENT UTILE!!!
				if (!monAni.estVivant())
				{	
					//je l'enleve du terrain ou il se trouve
					this.terrain[monAni.obtientPosition().obtientLigne()][monAni.obtientPosition().obtientColonne()].assigneMonAnimal(null);
				}
				else
				{
					// mon animal est en vie
					aAgi = true;

					// JE FAIS AGIR MON ANIMAL
					// je supprime mon animal de mon terrain
					this.terrain[monAni.obtientPosition().obtientLigne()][monAni.obtientPosition().obtientColonne()].assigneMonAnimal(null);
					// je fais agir mon animal...
					monAni.gererAnimaux(this.obtientParcellesPortee(monAni.obtientTypeAnimal().champVision() , monAni.obtientPosition()));

					// DESTIN DE MON ANIMAL
					// Je supprime mon animal si il est mort
					if (monAni.estVivant())
					{
						// je met mon animal dans sa nouvelle case...
						this.terrain[monAni.obtientPosition().obtientLigne()][monAni.obtientPosition().obtientColonne()].assigneMonAnimal(monAni);
					}
				}
			}
			else
			{
				aAgi = true;	
			}			
		}
			
		////////////
		// PLANTE //
		////////////

		// faire pousser et se reproduire les plantes, pomper l'eau dans les parcelles, etc...
		// eliminer les plantes mortes
		
		ligne = monRand.nextInt(this.terrain.length);
		colonne = monRand.nextInt(this.terrain[0].length);

		if (terrain[ligne][colonne].estPlante())
		{
			// GERER PLANTE
			terrain[ligne][colonne].obtientMonVegetal().gererPlante(this.obtientParcellesPortee(terrain[ligne][colonne].obtientMonVegetal().obtientRayonDispersion(), terrain[ligne][colonne].obtientMonVegetal().obtientPosition()));
			if (terrain[ligne][colonne].obtientMonVegetal().obtientSante() <= 0)
			{
				terrain[ligne][colonne].assigneMonVegetal(null);
			}
		}
		// pas forcement utile
		// je me débarasse des animaux mort au cas ou...
		if (terrain[ligne][colonne].estAnimal())
		{
		// SUPPRIMER ANIMAUX MORTS AU PASSAGE
			if (!terrain[ligne][colonne].obtientMonAnimal().estVivant())
			{
				// enlever l'animal de la parcelle
				terrain[ligne][colonne].assigneMonAnimal(null);
			}
		}
	}
	
	/**
	 * Méthode permettant de faire tourner le jeu tant que l'on a pas une interuption clavier
	 * Condition de fin: plus d'animaux...
	 */
	public void faireTournerMonde()
	{
		boolean continuer = true;
		while (this.mesPrecipitations.size() != 0)
		{
			this.genererUnTour();
		}
		// afficher le monde entier...
		//méthode d'affichage
		// TODO: faire la methode d'affichage
	}

	/**
	 * Méthode permettant d'obtenir une representation textuelle de l'objet
	 * Structure: liste de toutes les parcelles, une parcelle par ligne
	 * 	
	 * @return la representation textuelle du terrain
	 */
	public String toString()
	{
		// VERSION TEMPORAIRE
		String maSortie = new String("");
		for (int i = 0; i<this.terrain.length;i++)
		{
			for (int j = 0; j<this.terrain[0].length;j++)
			{
				maSortie+=this.terrain[i][j]+"\n";
			}
		}
		return maSortie;	
	}
}
